Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagate unsafety to definitions that depend on unsafe definitions #379

Merged
merged 12 commits into from
Jun 10, 2024

Conversation

edusporto
Copy link
Contributor

Related to #362.

This PR kind of fixes the issue, but not really. While this new version does return an error in the previously thought "correct" version of the program, which is expected since it actually performed unsafe operations, it doesn't error in the second version, which does the same. Returning an error in this version is much harder, since it's not cloning an unsafe definition, but an unsafe expression. According to Nicolas, that could be solved with an affine type system.

src/ast.rs Outdated
Comment on lines 493 to 504
pub fn direct_dependencies<'name>(&'name self) -> BTreeSet<&'name str> {
match self {
Tree::Ref { nam } => BTreeSet::from([nam.as_str()]),
Tree::Con { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Dup { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Opr { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Swi { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Num { val } => BTreeSet::new(),
Tree::Var { nam } => BTreeSet::new(),
Tree::Era => BTreeSet::new(),
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing a recursive function like this may not be the best idea since we still don't have a way to increase stack size as needed like in the hvm-64 repo. I think it's fine for now to add this function like this, since pretty much all the other functions related to trees are also recursive and if this one fails, they do too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it becomes a problem we can just write this imperatively

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is quadratic, due to repeatedly cloning these btreesets; (@a (@b (@c (@d (@e ...))))) will take triangular time (which is asymptotically quadratic).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is quadratic, due to repeatedly cloning these btreesets; (@a (@b (@c (@d (@e ...))))) will take triangular time (which is asymptotically quadratic).

Thanks! I thought it would be similar to merging in merge sort, but I tested it and it really is slow. It should be better now.

println!("only testing rust implementation for {path:?}");
return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the possibility to only run tests in the Rust implementation, since the C one doesn't throw an error in this same case. This is something to discuss - should the C and CUDA implementations also "panic" printing this error and stopping the program?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine as is, and is in line with the other test-io pattern.

Copy link
Contributor

@enricozb enricozb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I understand correctly, this doesn't account for user-introduced unsafety? It only accounts for when users reference an unsafe function?

src/ast.rs Outdated
Comment on lines 493 to 504
pub fn direct_dependencies<'name>(&'name self) -> BTreeSet<&'name str> {
match self {
Tree::Ref { nam } => BTreeSet::from([nam.as_str()]),
Tree::Con { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Dup { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Opr { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Swi { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Num { val } => BTreeSet::new(),
Tree::Var { nam } => BTreeSet::new(),
Tree::Era => BTreeSet::new(),
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it becomes a problem we can just write this imperatively

println!("only testing rust implementation for {path:?}");
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine as is, and is in line with the other test-io pattern.

@edusporto
Copy link
Contributor Author

So if I understand correctly, this doesn't account for user-introduced unsafety? It only accounts for when users reference an unsafe function?

Depends on what you mean by "user-introduced", since users also introduce definitions. It will only catch unsafe operations if the unsafe operation is contained in a definition, and a reference to that definition is directly duplicated.

For example, exponentiation of church naturals (which I think should behave poorly in IC?)

unsf = @f @x (f (f x))
main = (unsf unsf)

This currently gets compiled to

@main = a
  & @unsf ~ (@unsf a)

@unsf = ({(b c) (a b)} (a c))

which is correctly marked as unsafe. We could also do

@main = a
  & ({(b c) (d b)} (d c)) ~ (({(b c) (d b)} (d c)) a)

which is not marked as unsafe, and does not terminate (loops forever)

@edusporto
Copy link
Contributor Author

@developedby this PR breaks (surprisingly only) one of the tests in Bend, guide_shader_dummy.bend:

# given a shader, returns a square image
def render(depth, shader):
  bend d = 0, i = 0:
    when d < depth:
      color = (fork(d+1, i*2+0), fork(d+1, i*2+1))
    else:
      width = depth / 2
      color = shader(i % width, i / width)
  return color

# given a position, returns a color
# for this demo, it just busy loops
def demo_shader(x, y):
  bend i = 0:
    when i < 10:
      color = fork(i + 1)
    else:
      color = 0x000001
  return color

# renders a 256x256 image using demo_shader
def main:
  return render(5, demo_shader)

demo_shader is marked as unsafe since it duplicates i, and the program fails with the "ERROR: attempt to clone a non-affine global reference." error when render duplicates that function.

This is a very natural program to write, and the safety check this PR introduces will probably break many more programs created by users of Bend. I'm not sure how to deal with this... Should we really add this check in the way it's done by this PR, or should we do something else?

@developedby
Copy link
Member

@developedby this PR breaks (surprisingly only) one of the tests in Bend, guide_shader_dummy.bend:

# given a shader, returns a square image
def render(depth, shader):
  bend d = 0, i = 0:
    when d < depth:
      color = (fork(d+1, i*2+0), fork(d+1, i*2+1))
    else:
      width = depth / 2
      color = shader(i % width, i / width)
  return color

# given a position, returns a color
# for this demo, it just busy loops
def demo_shader(x, y):
  bend i = 0:
    when i < 10:
      color = fork(i + 1)
    else:
      color = 0x000001
  return color

# renders a 256x256 image using demo_shader
def main:
  return render(5, demo_shader)

demo_shader is marked as unsafe since it duplicates i, and the program fails with the "ERROR: attempt to clone a non-affine global reference." error when render duplicates that function.

This is a very natural program to write, and the safety check this PR introduces will probably break many more programs created by users of Bend. I'm not sure how to deal with this... Should we really add this check in the way it's done by this PR, or should we do something else?

Yes, this is the intended behaviour and a big limitation of hvm32

@developedby
Copy link
Member

If we plan on continuing with hvm32 for a lot longer, I should probably add some monomorphization system to Bend to avoid hitting this limitation so often

src/ast.rs Outdated
Comment on lines 493 to 504
pub fn direct_dependencies<'name>(&'name self) -> BTreeSet<&'name str> {
match self {
Tree::Ref { nam } => BTreeSet::from([nam.as_str()]),
Tree::Con { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Dup { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Opr { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Swi { fst, snd } => &fst.direct_dependencies() | &snd.direct_dependencies(),
Tree::Num { val } => BTreeSet::new(),
Tree::Var { nam } => BTreeSet::new(),
Tree::Era => BTreeSet::new(),
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is quadratic, due to repeatedly cloning these btreesets; (@a (@b (@c (@d (@e ...))))) will take triangular time (which is asymptotically quadratic).

src/ast.rs Outdated Show resolved Hide resolved
src/ast.rs Outdated Show resolved Hide resolved
src/ast.rs Outdated Show resolved Hide resolved
@edusporto
Copy link
Contributor Author

All suggestions were resolved
Any other details or should we merge it? @enricozb @developedby @tjjfvi

@tjjfvi
Copy link
Contributor

tjjfvi commented Jun 10, 2024

fine by me, although slight nit: direct_dependencies_reversed could instead be named direct_dependents (with similar naming changes made elsewhere)

Change "dependency reversed" to "dependent"
src/ast.rs Outdated Show resolved Hide resolved
@enricozb enricozb self-requested a review June 10, 2024 19:20
@edusporto edusporto merged commit d153497 into main Jun 10, 2024
3 of 4 checks passed
@edusporto edusporto deleted the fix-safety-checking branch June 10, 2024 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants